#pragma once

#include "curlwork/curlwork.hpp"

namespace curlwork {

struct queue_reactor
{
    multi_handle_type handle_;
    std::size_t size_;

    queue_reactor() throw()
        : handle_(curl_multi_init())
        , size_(0)
    {   }

    ~queue_reactor() throw()
    {
        curl_multi_cleanup(handle_);
    }

    template<typename T>
    void add(const T& op)
    {
        merror_type e = curl_multi_add_handle(handle_, op.handle());
        if (e != no_error)
                throw std::runtime_error(description(e));

        ++size_;
    }
/*
    template<typename F>
    void set(const F& mod)
    {
        mod.apply(*this);
    }
*/
    // http://curl.haxx.se/libcurl/c/multi-app.html
    bool react()
    {
        // some curl issue fix
        int result = perform();

        if (result)
        {
            int size = -1;

            fd_set fr = { };
            fd_set fw = { };
            fd_set fe = { };

            merror_type e = curl_multi_fdset(handle_, &fr, &fw, &fe, &size);
            if (e != no_error)
                throw std::runtime_error(description(e));

            timeval t = timeout();

            int res = select(size + 1, &fr, &fw, &fe, &t);
            if (res == -1)
                throw std::runtime_error("select errror");
        }

        if (update())
            perform();

        return !empty();
    }

    bool empty() const throw()
    {
        return size_ == 0;
    }

    std::size_t size() const throw()
    {
        return size_;
    }

private:

    queue_reactor(const queue_reactor&);
    queue_reactor& operator=(const queue_reactor&);

    bool update() throw()
    {
        int count = 0;
        bool res = false;
        msg_type *msg = 0;

        while ((msg = curl_multi_info_read(handle_, &count)))
        {
            res = true;
            if (msg->msg == CURLMSG_DONE)
            {
                update_handle(msg->easy_handle, msg->data.result);
            }
        }

        return res;
    }

    int perform()
    {
        int result = 0;
        merror_type e = CURLM_OK;

        do
        {
            e = curl_multi_perform(handle_, &result);
            // about this
            // http://curl.haxx.se/libcurl/c/curl_multi_perform.html
        } while (e == CURLM_CALL_MULTI_PERFORM);

        if (e != no_error)
            throw std::runtime_error(description(e));

        return result;
    }

    timeval timeout()
    {
        long t = -1;
        timeval timeout = { 1, 0 };

        merror_type e = curl_multi_timeout(handle_, &t);
        if (e != no_error)
            throw std::runtime_error(description(e));

        if (t >= 0)
        {
            timeout.tv_sec = t / 1000;

            if (timeout.tv_sec > 1)
                timeout.tv_sec = 1;
            else
                timeout.tv_usec = (t % 1000) * 1000;
        }

        return timeout;
    }

    void update_handle(easy_handle_type handle, error_type result)
    {
        merror_type e = curl_multi_remove_handle(handle_, handle);
        if (e != no_error)
              throw std::runtime_error(description(e));

        --size_;

        delegate *d = 0;

        error_type ee = curl_easy_getinfo(handle,
                                          CURLINFO_PRIVATE,
                                          &d);
        if (ee != no_error)
            throw std::runtime_error(description(ee));

        d->apply(result);
    }
};

} // namespace curlwork
